#include "Grass.h"

#include "../Renderer.h"
#include "../PhysicsLogic.h"
#include "../Game.h"
#include "../Util/Physx.h"
#include "../Stream.h"

#include <NxCooking.h>

const int Grass::cellCountX = 1;
const int Grass::cellCountY = 3;
const float Grass::cellW = 0.2f*5*4;
const float Grass::cellH = 0.7f*2;
const float Grass::thickness = Grass::cellH*0.1f;
const float Grass::bendingStiffness = 1.0f;
const float Grass::density = 1.0f;
const unsigned int Grass::grassCount = 2000;

NxClothMeshDesc Grass::cmDesc;
NxClothMesh* Grass::clothMesh;
NxForceFieldLinearKernel* Grass::linearKernel;

SurfaceRenderingData Grass::srdCloth;

ArrayList<Grass*> Grass::grassList;

NxReal *Grass::textCoords = NULL;

Grass::Grass(const NxVec3& pos, const NxVec3& orientation) {
	const NxU32 tearMemoryFactor = 1;
	NxU32 maxVertices = tearMemoryFactor * cmDesc.numVertices;
	NxU32 maxIndices = 3 * cmDesc.numTriangles;
	NxU32 *numVertices = new NxU32(cmDesc.numVertices);
	NxU32 *numIndices  = new NxU32(3 * cmDesc.numTriangles);

	NxVec3 *vertices = (NxVec3*)calloc(maxVertices, sizeof(NxVec3));
	NxVec3 *normals = (NxVec3*)calloc(maxVertices, sizeof(NxVec3));
	NxU32 *indices = (NxU32*)calloc(maxIndices, sizeof(NxU32));

	NxMeshData receiveBuffers;
	receiveBuffers.verticesPosBegin = &(vertices[0].x);
	receiveBuffers.verticesPosByteStride = sizeof(NxVec3);
	receiveBuffers.verticesNormalBegin = &(normals[0].x);
	receiveBuffers.verticesNormalByteStride = sizeof(NxVec3);
	receiveBuffers.maxVertices = maxVertices;
	receiveBuffers.numVerticesPtr = numVertices;

	receiveBuffers.indicesBegin = &(indices[0]);
	receiveBuffers.indicesByteStride = sizeof(NxU32);
	receiveBuffers.maxIndices = maxIndices;
	receiveBuffers.numIndicesPtr = numIndices;

	NxMat33 m;
	rotationVectorToMatrix(orientation, &m);

	NxClothDesc cDesc;
	cDesc.flags |= NX_CLF_BENDING;
	cDesc.flags |= NX_CLF_VISUALIZATION;
	if(PhysicsLogic::IsHardwareAccelerationSupported()) {
		cDesc.flags |= NX_CLF_HARDWARE;
	}
	cDesc.density = 1.0f;
	cDesc.bendingStiffness = bendingStiffness;
	cDesc.thickness = thickness;
	cDesc.selfCollisionThickness = thickness;
	cDesc.clothMesh = clothMesh;
	cDesc.meshData = receiveBuffers;
	cDesc.globalPose.t = pos + NxVec3(0,-cellH,0);
	cDesc.globalPose.M = m;

	cloth = PhysicsLogic::GetScene()->createCloth(cDesc);
	for(NxU32 i=0; i<(cellCountX+1); ++i) {
		for(int g=0; g<grassCount; ++g) {
			const int vi1 = i+(*numVertices/grassCount)*g;
			const int vi2 = i+(*numVertices/grassCount)*g+(cellCountX+1);
			cloth->attachVertexToGlobalPosition(vi1, cloth->getPosition(vi1));
			cloth->attachVertexToGlobalPosition(vi2, cloth->getPosition(vi2));
		}
	}
}

Grass::~Grass() {
	NxMeshData& meshData = cloth->getMeshData();

	delete meshData.numVerticesPtr;
	delete meshData.numIndicesPtr;
	free(meshData.verticesPosBegin);
	free(meshData.verticesNormalBegin);
	free(meshData.indicesBegin);

	PhysicsLogic::GetScene()->releaseCloth(*cloth);
}

void Grass::Load() {
	Game::log.LogDebug("Grass: generating...");

	const float range = 0.03f;
	const int rRange = 6000;

	const int numVerticesStrip = (cellCountX+1) * (cellCountY+1);
	const int numTrianglesStrip = cellCountX*cellCountY*2;
	cmDesc.numVertices = numVerticesStrip * grassCount;
	cmDesc.numTriangles = numTrianglesStrip * grassCount;
	cmDesc.pointStrideBytes = sizeof(NxVec3);
	cmDesc.triangleStrideBytes = 3*sizeof(NxU32);
	cmDesc.points = (NxVec3*)malloc(sizeof(NxVec3)*cmDesc.numVertices);
	cmDesc.triangles = (NxU32*)malloc(sizeof(NxU32)*cmDesc.numTriangles*3);
	cmDesc.flags = 0;
	for(int ci=0; ci<grassCount; ++ci) {
		const float px = ((rand()%(rRange*2))-rRange)*range;
		const float pz = ((rand()%(rRange*2))-rRange)*range;
		int i,j;
		NxVec3 *p = &(((NxVec3*)(cmDesc.points))[numVerticesStrip*ci]);
		for (i = 0; i <= cellCountY; i++) {
			for (j = 0; j <= cellCountX; j++) {
				p->set(cellW*j+px, cellH*i, pz);
				p++;
			}
		}
		NxU32 *id = &(((NxU32*)(cmDesc.triangles))[numTrianglesStrip*ci*3]);
		for (i = 0; i < cellCountY; i++) {
			for (j = 0; j < cellCountX; j++) {
				NxU32 i0 = i * (cellCountX+1) + j + numVerticesStrip*ci;
				NxU32 i1 = i0 + 1;
				NxU32 i2 = i0 + (cellCountX+1);
				NxU32 i3 = i2 + 1;
				if((j+i)%2) {
					*id++ = i0;
					*id++ = i2;
					*id++ = i1;
					*id++ = i1;
					*id++ = i2;
					*id++ = i3;
				} else {
					*id++ = i0;
					*id++ = i2;
					*id++ = i3;
					*id++ = i0;
					*id++ = i3;
					*id++ = i1;
				}
			}
		}
	}

	//calculating texture coords
	textCoords = new NxReal[cmDesc.numVertices*2];

	for(NxU32 i = 0; i < cmDesc.numTriangles; ++i)	{
		for(int k = 0; k < 3; ++k) {
			const NxU32 id = ((NxU32*)cmDesc.triangles)[i*3 + k];

			const NxU32 texID = id % numVerticesStrip;
			const NxU32 cellX = texID % (cellCountX+1);
			const NxU32 cellY = texID / (cellCountX+1);
			const float tx = float(cellX)/float(cellCountX);
			const float ty = 1.0f-float(cellY)/float(cellCountY);

			textCoords[2*id] = tx;
			textCoords[2*id+1] = ty;
		}
	}

	//cooking:

	NxCookingInterface *cookingInterface = NxGetCookingLib(NX_PHYSICS_SDK_VERSION);
	if(!cookingInterface) {
		Game::log.LogError("Failed to get cooking lib.");
		return;
	}

	cookingInterface->NxInitCooking();

	MemoryWriteBuffer wb;
	if(!cookingInterface->NxCookClothMesh(cmDesc, wb)) {
		Game::log.LogError("Failed to cook cloth mesh.");
		return;
	}
	MemoryReadBuffer rb(wb.data);
	clothMesh = PhysicsLogic::GetPhysicsSDK()->createClothMesh(rb);

	//rendering data:

	//srdCloth.SetColor(0.5f, 1.0f, 0.5f, 1.0f);
	srdCloth.SetTexture(Textures::TEXTURE_GRASS);

	//wind:

	const float windBoxHeight = cellCountY * cellH;

	NxForceFieldLinearKernelDesc linearKernelDesc;

	linearKernel = PhysicsLogic::GetScene()->createForceFieldLinearKernel(linearKernelDesc);

	NxForceFieldDesc fieldDesc;
	fieldDesc.kernel = linearKernel;
	fieldDesc.coordinates = NX_FFC_CARTESIAN;
	fieldDesc.pose.t = NxVec3(0,windBoxHeight*0.5f,0);
	fieldDesc.rigidBodyType = NX_FF_TYPE_NO_INTERACTION;

	NxForceField *forceField; 
	forceField = PhysicsLogic::GetScene()->createForceField(fieldDesc);

	const float boxSize = range*rRange+1.0f;
	NxBoxForceFieldShapeDesc boxDesc;
	boxDesc.dimensions = NxVec3(boxSize, windBoxHeight*0.5f, boxSize);

	NxForceFieldShape *boxForceFieldShape = forceField->getIncludeShapeGroup().createShape(boxDesc);

	SetWind(PhysicsLogic::GetWind());

	Game::log.LogDebug("Grass: done.");
}

void Grass::SetWind(const NxVec3& force) {
	linearKernel->setConstant(force);
	linearKernel->setNoise(force*0.1f);
}

void Grass::Add(const NxVec3& pos, const NxVec3& orientation) {
	grassList.Add(new Grass(pos, orientation));
}

void Grass::DeleteAll() {
	while(!grassList.Empty()) {
		delete grassList.GetFirst();;
		grassList.RemoveFirst();
	}
}

int Grass::GetCount() {
	return grassList.Size();
}

void Grass::DrawAll() {
	for(int ci=0; ci<grassList.Size(); ++ci) {
		grassList[ci]->Draw();
	}
}

void Grass::Draw() {
	const NxMeshData& mData = cloth->getMeshData();

	const NxU32 indicesCount = *mData.numIndicesPtr/3;
	const NxU32 verticesCount = *mData.numVerticesPtr;
	const NxU32 verticesPerStrip = (verticesCount/grassCount);

	const NxU32 *triangles = (NxU32*)mData.indicesBegin;
	const NxVec3 *vertices = (NxVec3*)mData.verticesPosBegin;
	const NxVec3 *normals = (NxVec3*)mData.verticesNormalBegin;

	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

	Renderer::SetMaterial(srdCloth);

	glDisable(GL_CULL_FACE);
	//normal render code, immediate mode
	/*for(NxU32 i = 0; i < indicesCount; ++i)	{
		glBegin(GL_TRIANGLES);
		for(int k = 0; k < 3; ++k) {
			const NxU32 id = (NxU32)triangles[i*3 + k];

			const NxVec3& tn = normals[id];
			glNormal3f(tn.x, tn.y, tn.z);

			glTexCoord2f(textCoords[id*2], textCoords[id*2+1]);

			const NxVec3& v = vertices[id];
			glVertex3f(v.x, v.y, v.z);
		}
		glEnd();
	}*/

	//render code with vertex arrays
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glVertexPointer(3, GL_FLOAT, 0, &vertices[0]);
	glNormalPointer(GL_FLOAT, 0, &normals[0]);
	glTexCoordPointer(2, GL_FLOAT, 0, &textCoords[0]);

	glDrawElements(GL_TRIANGLES, indicesCount*3 , GL_UNSIGNED_INT, &triangles[0]);

	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);

	glEnable(GL_CULL_FACE);
}
